/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package xenon3d.scene;

import javax.media.opengl.GL2;

/**
 * The PolygonAttributes object defines attributes for rendering polygon
 * primitives.
 * 
 * @author Volker Everts
 * @version 0.1 - 10.12.2009: Created
 * @version 0.2 - 12.11.2011: Adapted
 */
public class PolygonAttributes extends Attributes {

    // <editor-fold defaultstate="collapsed" desc=" Static Attributes ">
    
    /** The default polygon attributes. */
    static final PolygonAttributes DEFAULT = new PolygonAttributes();
    
    /** The last active PolygonAttributes. */
    static PolygonAttributes last = DEFAULT;
    
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" Public Enumerations ">
    
    /**
     * An enum for setting the polygon rasterization mode.
     */
    public static enum PolygonMode {
        
        /**
         * The polygon is rendered by filling the interior between the vertices.
         * This is the default mode.
         */
        Fill (GL2.GL_FILL),
        
        /**
         * The polygon is rendered as lines drawn between consecutive vertices
         * ("Wireframe Model").
         */
        Line (GL2.GL_LINE),
        
        /**
         * The polygon is rendered as points drawn at the vertices ("Point Cloud
         * Model").
         */
        Point (GL2.GL_POINT);
        
        /** The OpenGL value of the polygon render flag. */
        int value;
        
        /**
         * Creates a new PolygonAttributes object.
         * @param value the OpenGL value of the polygon render flag
         */
        PolygonMode(int value) {
            this.value = value;
        }
        
    } // end enum PolygonMode
    
    
    /**
     * An enum for setting the polygon cull face.
     */
    public static enum CullFace {
        
        /** Disables face culling. */
        None (GL2.GL_NONE),
        
        /** Enables back face culling. */
        Back (GL2.GL_BACK),
        
        /** Enables front face culling. */
        Front (GL2.GL_FRONT),
        
        /** Enables front and back face culling. */
        Both (GL2.GL_FRONT_AND_BACK);
        
        /** The OpenGL value of the cull face flag. */
        int value;
        
        /**
         * Creates a new CullFace object.
         * @param value the OpenGL value of the cull face flag
         */
        CullFace(int value) {
            this.value = value;
        }
        
    } // end enum CullFace
    
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc=" Private Fields ">
    
    /** The polygon offset factor. */
    private float factor = 0.0f;
    
    /** The polygon offset units. */
    private float units = 0.0f;
    
    /** The polygon offset flag. */
    private boolean offset = false;
    
    /** The polygon antialiasing flag. */
    private boolean antialiasing = false;
    
    /** The back face normal flip value. */
    private boolean backFaceNormalFlip = false;
    
    /** The polygon mode. */
    private PolygonMode mode = PolygonMode.Fill;
    
    /** The polygon cull face value. */
    private CullFace cullFace = CullFace.Back;
    
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc=" Initialization ">
    
    /**
     * Creates a new PolygonAttributes object using default parameter settings.
     * The default settings are:<p>
     * <ul>
     * <li>PolygonMode: Fill</li>
     * <li>CullFace: Back</li>
     * <li>BackFaceNormalFlip: false</li>
     * <li>PolygonOffset: false</li>
     * <li>PolygonOffsetFactor: 0.0f</li>
     * <li>PolygonOffsetUnits: 0.0f</li>
     * </ul>
     */
    public PolygonAttributes() {}
    
    /**
     * Creates a new PolygonAttributes object using the specified render settings.
     * @param cullFace the cull face value
     * @param offset the polygon offset factor
     * @param backFaceNormalFlip if true, back face normal flipping is enabled
     * @param antialiasing  if true, polygon antialiasing is enabled
     */
    public PolygonAttributes(CullFace cullFace, float offset, boolean backFaceNormalFlip, boolean antialiasing) {
        if (cullFace == null) throw new IllegalArgumentException();
        this.cullFace = cullFace;
        factor = offset;
        this.offset = factor != 0.0f;
        this.backFaceNormalFlip = backFaceNormalFlip;
        this.antialiasing = antialiasing;
    }
    
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc=" Public Properties ">
    
    /**
     * Sets whether polygon antialiasing should be enabled.
     * @param enable if true, polygon antialiasing is enabled
     */
    public final void setPolygonAntialiasing(boolean enable) {
        checkCapability(Capability.AllowPolygonAttributesWrite);
        antialiasing = enable;
    }
    
    /**
     * Returns whether polygon antialiasing is enabled.
     * @return true, if polygon antialiasing is enabled
     */
    public final boolean isPolygonAntialiasing() {
        return antialiasing;
    }
    
    /**
     * Sets the polygon mode.
     * @param mode the new polygon mode
     */
    public final void setPolygonMode(PolygonMode mode) {
        checkCapability(Capability.AllowPolygonAttributesWrite);
        if (mode == null) throw new IllegalArgumentException();
        this.mode = mode;
    }
    
    /**
     * Returns the polygon mode.
     * @return the current polygon mode
     */
    public final PolygonMode getPolygonMode() {
        return mode;
    }
    
    /**
     * Sets the polygon face culling mode.
     * @param cullFace the cull face value
     */
    public final void setCullFace(CullFace cullFace) {
        checkCapability(Capability.AllowPolygonAttributesWrite);
        if (cullFace == null) throw new IllegalArgumentException();
        this.cullFace = cullFace;
    }
    
    /**
     * Returns the polygon face culling mode.
     * @return the cull face value
     */
    public final CullFace getCullFace() {
        return cullFace;
    }
    
    /**
     * Sets whether back face normal flipping should be enabled.
     * @param enable if true, back face normal flipping is enabled
     */
    public final void setBackFaceNormalFlip(boolean enable) {
        checkCapability(Capability.AllowPolygonAttributesWrite);
        backFaceNormalFlip = enable;
    }
    
    /**
     * Returns whether back face normal flipping is enabled.
     * @return true, if back face normal flipping is enabled
     */
    public final boolean getBackFaceNormalFlip() {
        return backFaceNormalFlip;
    }
    
    /**
     * Sets the polygon offset factor and the constant depth offset unit.
     * @param factor the polygon offset factor
     * @param units the depth offset unit
     */
    public final void setPolygonOffset(float factor, float units) {
        checkCapability(Capability.AllowPolygonAttributesWrite);
        this.factor = factor;
        this.units = units;
        offset = factor != 0.0f || units != 0.0f;
    }
    
    /**
     * Returns whether there are valid polygon offset settings.
     * @return true, if there are polygon offset settings
     */
    public final boolean isPolygonOffset() {
        return offset;
    }
    
    /**
     * Returns the polygon offset factor.
     * @return the current polygon offset factor
     */
    public final float getPolygonOffsetFactor() {
        return factor;
    }
    
    /**
     * Returns the polygon offset units.
     * @return the current polygon offset units
     */
    public final float getPolygonOffsetUnits() {
        return units;
    }
    
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc=" Package Private Methods (OpenGL) ">
    
    @Override
    void apply(GL2 gl) {
        if (this == last) return;
        if (antialiasing != last.antialiasing) {
            if (antialiasing) gl.glEnable(GL2.GL_POLYGON_SMOOTH);
            else gl.glDisable(GL2.GL_POLYGON_SMOOTH);
        }
        if (cullFace != last.cullFace) {
            switch (cullFace) {
                case None:
                    gl.glDisable(GL2.GL_CULL_FACE);
                    break;
                case Back:
                    gl.glEnable(GL2.GL_CULL_FACE);
                    gl.glCullFace(GL2.GL_BACK);
                    break;
                case Front:
                    gl.glEnable(GL2.GL_CULL_FACE);
                    gl.glCullFace(GL2.GL_FRONT);
                    break;
                case Both:
                    gl.glEnable(GL2.GL_CULL_FACE);
                    gl.glCullFace(GL2.GL_FRONT_AND_BACK);
                    break;
            }
        }
        if (backFaceNormalFlip != last.backFaceNormalFlip) {
            // FIXME
        }
        if (mode != last.mode) gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, mode.value);
        if (offset != last.offset) {
            if (offset) {
                gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL);
                gl.glEnable(GL2.GL_POLYGON_OFFSET_LINE);
                gl.glEnable(GL2.GL_POLYGON_OFFSET_POINT);
                gl.glPolygonOffset(factor, units);
            }
            else {
                gl.glDisable(GL2.GL_POLYGON_OFFSET_LINE);
                gl.glDisable(GL2.GL_POLYGON_OFFSET_FILL);
                gl.glDisable(GL2.GL_POLYGON_OFFSET_POINT);
            }
        }
        last = this;
    }
    
    // </editor-fold>
    
} // end class PolygonAttributes